<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>烟花特效</title>
    <style>
      body {
        background-color: #000000;
        margin: 0px;
        overflow: hidden;
      }
    </style>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js"></script>
  </head>
  <body>
    <script>
      var SCREEN_WIDTH = window.innerWidth,
        SCREEN_HEIGHT = window.innerHeight,
        mousePos = {
          x: 400,
          y: 300,
        },
        // create canvas
        canvas = document.createElement("canvas"),
        context = canvas.getContext("2d"),
        particles = [],
        rockets = [],
        MAX_PARTICLES = 400,
        colorCode = 0;

      // init
      $(document).ready(function () {
        document.body.appendChild(canvas);
        canvas.width = SCREEN_WIDTH;
        canvas.height = SCREEN_HEIGHT;
        setInterval(launch, 800);
        setInterval(loop, 1000 / 50);
      });

      // update mouse position
      $(document).mousemove(function (e) {
        e.preventDefault();
        mousePos = {
          x: e.clientX,
          y: e.clientY,
        };
      });

      // launch more rockets!!!
      $(document).mousedown(function (e) {
        for (var i = 0; i < 5; i++) {
          launchFrom((Math.random() * SCREEN_WIDTH * 2) / 3 + SCREEN_WIDTH / 6);
        }
      });

      function launch() {
        launchFrom(mousePos.x);
      }

      function launchFrom(x) {
        if (rockets.length < 10) {
          var rocket = new Rocket(x);
          rocket.explosionColor = Math.floor((Math.random() * 360) / 10) * 10;
          rocket.vel.y = Math.random() * -3 - 4;
          rocket.vel.x = Math.random() * 6 - 3;
          rocket.size = 8;
          rocket.shrink = 0.999;
          rocket.gravity = 0.01;
          rockets.push(rocket);
        }
      }

      function loop() {
        // update screen size
        if (SCREEN_WIDTH != window.innerWidth) {
          canvas.width = SCREEN_WIDTH = window.innerWidth;
        }
        if (SCREEN_HEIGHT != window.innerHeight) {
          canvas.height = SCREEN_HEIGHT = window.innerHeight;
        }

        // clear canvas
        context.fillStyle = "rgba(0, 0, 0, 0.05)";
        context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

        var existingRockets = [];

        for (var i = 0; i < rockets.length; i++) {
          // update and render
          rockets[i].update();
          rockets[i].render(context);

          // calculate distance with Pythagoras
          var distance = Math.sqrt(
            Math.pow(mousePos.x - rockets[i].pos.x, 2) +
              Math.pow(mousePos.y - rockets[i].pos.y, 2)
          );

          // random chance of 1% if rockets is above the middle
          var randomChance =
            rockets[i].pos.y < (SCREEN_HEIGHT * 2) / 3
              ? Math.random() * 100 <= 1
              : false;

          /* Explosion rules
		 - 80% of screen
		 - going down
		 - close to the mouse
		 - 1% chance of random explosion
		 */
          if (
            rockets[i].pos.y < SCREEN_HEIGHT / 5 ||
            rockets[i].vel.y >= 0 ||
            distance < 50 ||
            randomChance
          ) {
            rockets[i].explode();
          } else {
            existingRockets.push(rockets[i]);
          }
        }

        rockets = existingRockets;

        var existingParticles = [];

        for (var i = 0; i < particles.length; i++) {
          particles[i].update();

          // render and save particles that can be rendered
          if (particles[i].exists()) {
            particles[i].render(context);
            existingParticles.push(particles[i]);
          }
        }

        // update array with existing particles - old particles should be garbage collected
        particles = existingParticles;

        while (particles.length > MAX_PARTICLES) {
          particles.shift();
        }
      }

      function Particle(pos) {
        this.pos = {
          x: pos ? pos.x : 0,
          y: pos ? pos.y : 0,
        };
        this.vel = {
          x: 0,
          y: 0,
        };
        this.shrink = 0.97;
        this.size = 2;

        this.resistance = 1;
        this.gravity = 0;

        this.flick = false;

        this.alpha = 1;
        this.fade = 0;
        this.color = 0;
      }

      Particle.prototype.update = function () {
        // apply resistance
        this.vel.x *= this.resistance;
        this.vel.y *= this.resistance;

        // gravity down
        this.vel.y += this.gravity;

        // update position based on speed
        this.pos.x += this.vel.x;
        this.pos.y += this.vel.y;

        // shrink
        this.size *= this.shrink;

        // fade out
        this.alpha -= this.fade;
      };

      Particle.prototype.render = function (c) {
        if (!this.exists()) {
          return;
        }

        c.save();

        c.globalCompositeOperation = "lighter";

        var x = this.pos.x,
          y = this.pos.y,
          r = this.size / 2;

        var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
        gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")");
        gradient.addColorStop(
          0.8,
          "hsla(" + this.color + ", 100%, 50%, " + this.alpha + ")"
        );
        gradient.addColorStop(1, "hsla(" + this.color + ", 100%, 50%, 0.1)");

        c.fillStyle = gradient;

        c.beginPath();
        c.arc(
          this.pos.x,
          this.pos.y,
          this.flick ? Math.random() * this.size : this.size,
          0,
          Math.PI * 2,
          true
        );

        c.closePath();
        c.fill();

        c.restore();
      };

      Particle.prototype.exists = function () {
        return this.alpha >= 0.1 && this.size >= 1;
      };

      function Rocket(x) {
        Particle.apply(this, [
          {
            x: x,
            y: SCREEN_HEIGHT,
          },
        ]);

        this.explosionColor = 0;
      }

      Rocket.prototype = new Particle();
      Rocket.prototype.constructor = Rocket;

      Rocket.prototype.explode = function () {
        var count = Math.random() * 10 + 80;

        for (var i = 0; i < count; i++) {
          var particle = new Particle(this.pos);
          var angle = Math.random() * Math.PI * 2;

          // emulate 3D effect by using cosine and put more particles in the middle
          var speed = Math.cos((Math.random() * Math.PI) / 2) * 15;

          particle.vel.x = Math.cos(angle) * speed;
          particle.vel.y = Math.sin(angle) * speed;

          particle.size = 10;

          particle.gravity = 0.2;
          particle.resistance = 0.92;
          particle.shrink = Math.random() * 0.05 + 0.93;

          particle.flick = true;
          particle.color = this.explosionColor;

          particles.push(particle);
        }
      };

      Rocket.prototype.render = function (c) {
        if (!this.exists()) {
          return;
        }

        c.save();

        c.globalCompositeOperation = "lighter";

        var x = this.pos.x,
          y = this.pos.y,
          r = this.size / 2;

        var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
        gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + this.alpha + ")");
        gradient.addColorStop(1, "rgba(0, 0, 0, " + this.alpha + ")");

        c.fillStyle = gradient;

        c.beginPath();
        c.arc(
          this.pos.x,
          this.pos.y,
          this.flick
            ? (Math.random() * this.size) / 2 + this.size / 2
            : this.size,
          0,
          Math.PI * 2,
          true
        );
        c.closePath();
        c.fill();

        c.restore();
      };
    </script>
  </body>
</html>
